8.3 Refactoring
 
Mit Refactoring wird im Visual Studio 2005 erstmals eine Technik eingeführt, die bisher nur durch das Einbinden zusätzlicher Tools zu nutzen war. Refactoring ist hauptsächlich dann sinnvoll einzusetzen, wenn die Software bereits fertig ist. Sie können mit dieser Technik Änderungen am Code vornehmen, ohne dass sich das Verhalten der Anwendung ändert.
Sie werden mir beipflichten, dass während des Entwicklungsprozesses insbesondere größerer und komplexerer Software oft der Überblick über den Code verloren geht. Codepassagen wiederholen sich, Methoden werden zu komplex, weil der Modularisierung des Programmcodes zu wenig Aufmerksamkeit geschenkt wird, Variablennamen sind unpassend vergeben – typische Phänomene bei vielen Projekten. Mit der Technik des Refactorings können Sie dazu beitragen, dass der Code überschaubarer und klarer strukturiert wird. Dazu bieten sich mehrere Verfahren an:
|
Methode extrahieren |
|
Umbenennen von Bezeichnern |
|
Felder einkapseln |
|
Schnittstellen extrahieren |
|
Lokale Variable auf Parameter heraufstufen |
|
Parameter entfernen |
|
Parameter neu anordnen |
In den deutschsprachigen Versionen von Visual Studio 2005 werden Sie den Begriff »Refactoring« nicht finden. Er wurde – bedauerlicherweise – in »Umgestaltung« übersetzt. Unter der deutschen Übersetzung sollten Sie nach Bedarf auch in der .NET-Dokumentation suchen.
8.3.1 Methode extrahieren
 
Der Sinn der Modularisierung ist es, viele kleine Methoden zu haben, die leichter zu überblicken und besser zu verstehen sind als lange, bildschirmfüllende. Der Methodenbezeichner sollte zudem so gewählt werden, dass man sofort weiß, was die Aufgabe der Methode ist. Gerät Ihnen bei der Entwicklung eine Methode »aus den Fugen«, müssen Sie die Codezeilen auffinden, die zusammengehören. Anschließend schneiden Sie diese aus und setzen sie in eine neue Methode ein. Anstelle der Codezeilen, die Sie ausgegliedert haben, setzen Sie den Aufruf der neuen Methode.
Dazu markieren Sie die Codezeilen, die in eine eigene Methode überführt werden sollen und wählen im Menü Umgestalten · Methode extrahieren. Die neue Methode wird mit dem ausgewählten Code erstellt, und der im Member ausgewählte Code durch einen Aufruf an die neue Methode ersetzt.
Als anschauliches Beispiel soll uns die folgende Methodendefinition dienen:
| public static double TestProc() {
|
| int intVar;
|
| intVar = 10;
|
| double result = Math.Pow(2, intVar);
|
| Console.WriteLine("Das Ergebnis lautet: {0}", result);
|
| return result;
|
| }
|
In Abbildung 8.14 ist das Szenario des Auslagerns von Code zu erkennen. Zu Demonstrationszwecken ist hier bis auf die Deklaration der lokalen Variablen intVar der gesamte Code markiert. Der Dialog öffnet sich, sobald Sie der Entwicklungsumgebung mitgeteilt haben, die markierten Codezeilen extrahieren zu wollen. Tragen Sie hier nur noch einen passenden Bezeichner für die neue Methode ein. Eine Vorschau zeigt die Signatur der Methode. Der Rest wird vom Visual Studio 2005 erledigt.
 Hier klicken, um das Bild zu vergrößern
Abbildung 8.14 Eingabe des Namens der neuen Methode
Das Ergebnis ist wie folgt:
| public static double TestProc() {
|
| int intVar;
|
| return ExtrahierteMethode(out intVar);
|
| }
|
| private static double ExtrahierteMethode(out int intVar) {
|
| intVar = 10;
|
| double result = Math.Pow(2, intVar);
|
| Console.WriteLine("Das Ergebnis lautet: {0}", result);
|
| return result;
|
| }
|
Die ausgeschnittenen Codezeilen werden durch den Aufruf der neuen Methode ersetzt. Ich habe mit Absicht die Anweisung zur Deklaration der lokalen Variablen nicht mit extrahiert. Wie sehr schön zu erkennen ist, berücksichtigt der Umgestaltungsprozess, dass intVar in der neuen Methode gesetzt und zur Berechnung einer Potenzzahl ausgewertet wird. Da intVar nur deklariert, jedoch nicht initialisiert ist, erwartet die extrahierte Methode einen out-Parameter. Wäre die Initialisierung vor Aufruf der extrahierten Methode erfolgt, wäre der Parameter ohne out erzeugt worden.
8.3.2 Umbenennen von Bezeichnern
 
Der erste Schritt zu einem verständlichen und gut interpretierbaren Code ist eine Namensgebung, die bereits Rückschlüsse auf den Zweck und Einsatz des betreffenden Mitglieds zulässt. Eine Methode namens CreateDatabase verrät bereits sehr viel, TestProc hingegen gar nichts (auch wenn ich in diesem Buch oft genau diesen Bezeichner verwendet habe).
Zum Ende eines Projekts, wenn man den geschriebenen Code noch einmal durchsieht, fallen oft Bezeichner ins Auge, die möglicherweise besser gewählt worden wären. Früher hätten Sie wahrscheinlich darauf verzichtet, den unpassenden Bezeichner durch einen beschreibenderen zu ersetzen, denn im Anschluss daran hätten Sie den gesamten Code durchforsten müssen, um ihn an allen Stellen auszutauschen. Abgesehen davon, dass sich dabei Fehler durch Versäumnisse einschleichen können, die dafür benötigte Zeit kann anders investiert werden.
Auch für diese Fälle bietet das Refactoring durch Umbenennung jetzt die Lösung. Umbenannt werden können Felder, Eigenschaften, lokale Variablen, Methoden, Namespaces und Typendefinitionen. Der Umbenennungsprozess erfasst dabei nicht nur Deklarationen und Aufrufe, er kann auch auf Kommentare und in Zeichenfolgen angewandt werden. Er ist zudem intelligent genug, alle Vorkommen des Elements zu erfassen.
Positionieren Sie den Cursor in dem betreffenden Bezeichner, und klicken Sie im Menü Umgestalten auf Umbenennen. Es öffnet sich ein Fenster, in dem Sie den neuen Namen eintragen. Zudem legen Sie hier auch fest, ob ebenfalls in Kommentaren und Zeichenfolgen nach dem alten Bezeichner gesucht werden soll. In Abbildung 8.15 sehen Sie das Fenster, wenn in TestProc die Variable intVar ausgewählt wird. Der neue Name ist noch nicht eingetragen.
 Hier klicken, um das Bild zu vergrößern
Abbildung 8.15 Umbenennen eines Elements
Vorausgewählt ist Vorschau der Verweisänderungen. Dahinter verbirgt sich ein Kontrollfenster, in dem alle Änderungen im Projekt farblich gekennzeichnet sind. Sie können im oberen Teil des Fensters die Änderungsvorschläge annehmen oder auch verwerfen. Das kann unter Umständen erforderlich sein, wenn Sie innerhalb des Codes zwei gleichnamige Bezeichner für verschiedene Elemente verwenden. Im unteren Teil wird der Code samt der vorgeschlagenen Änderung angezeigt.
 Hier klicken, um das Bild zu vergrößern
Abbildung 8.16 Umbenennen von Elementen
Visual Studio unterstützt die Umbenennung über die Projektgrenzen hinweg. Wenn Sie beispielsweise eine Konsolenanwendung entwickeln, die auf eine Klassenbibliothek verweist, werden beim Umbenennen eines Typs in der Klassenbibliothek auch die Verweise auf den Typ der Klassenbibliothek in der Konsolenanwendung aktualisiert.
8.3.3 Felder kapseln
 
Nach den Paradigmen der Objektorientierung sollten alle Felder gekapselt werden. .NET-Anwendungen lösen das mit Hilfe von Eigenschaften, die einen get- und einen set-Accesor haben. Nicht immer werden sie sich an die objektorientierten Grundsätze halten, Sie müssen es auch nicht. Daher kann es vorkommen, dass Sie eine Variable public definiert haben, aber zu einem späteren Zeitpunkt feststellen, dass die Kapselung in einer Eigenschaft unbedingt erforderlich ist. Über das Menü Umgestalten · Feld einkapseln ist das sehr schnell möglich, wenn Sie den Cursor vorher in die Codezeile des betreffenden Feldes gesetzt haben. Da mit der Kapselung möglicherweise auch eine Änderung des Aufrufs verbunden ist, werden im Code auch die Anweisungen herausgefiltert, die von der Änderung betroffen sind.
Eine öffentliche Felddeklaration mit public wird automatisch zu einer private-Deklaration. Im einem Fenster tragen Sie den Bezeichner der Eigenschaft ein. Per Konvention sollten öffentliche Bezeichner mit einem Großbuchstaben beginnen, die nichtöffentlichen mit einem Kleinbuchstaben. Wollen Sie sich daran halten, werden Sie vermissen, in einem Zug das gekapselte Feld umbenennen zu können (siehe auch Abbildung 8.17). Andererseits können Sie festlegen, welche Verweise aktualisiert werden sollen.
 Hier klicken, um das Bild zu vergrößern
Abbildung 8.17 Dialog zum Kapseln eines Feldes
Nach der Bestätigung wird ein weiterer Dialog geöffnet, ähnlich dem in Abbildung 8.16. Prüfen Sie hier vor der endgültigen Übernahme des Programmcodes, ob Sie alle Verweisänderungen akzeptieren können.
| public long LngVar1
|
| {
|
| get { return LngVar; }
|
| set { LngVar = value; }
|
| }
|
Handelt es sich bei dem zu kapselnden Feld um ein schreibgeschütztes readonly-Feld, wird der set-Zweig nicht erzeugt.
8.3.4 Schnittstelle extrahieren
 
Wenn mehrere Klassen, Strukturen oder gar Schnittstellen dieselbe Untermenge von Mitgliedern haben, kann es hilfreich sein, die gemeinsamen Mitglieder in eine Schnittstelle aufzunehmen. Positionieren Sie den Cursor in dem Typ, dessen Member Sie extrahieren wollen, und wählen Sie im Menü Umgestalten den Punkt Schnittstelle extrahieren.
Um zu sehen, wie der Umgestaltungsprozess arbeitet, wollen wir uns eine Klassendefinition ansehen, aus der ein Interface extrahiert werden soll.
| public class TestClass {
|
| public event EventHandler TestEvent;
|
| public int intVar;
|
| public int MyProperty {
|
| get {...}
|
| set {...}
|
| }
|
| public void TestProc1() {
|
| ...
|
| }
|
| private void TestProc2() {
|
| ...
|
| }
|
| internal void TestProc3() {
|
| ...
|
| }
|
| }
|
Die Klasse beschreibt ein Ereignis, ein öffentliches Feld und eine öffentliche Eigenschaft sowie darüber hinaus drei Methoden mit unterschiedlichen Zugriffsmodifizierern. Den Dialog zu dieser Klassendefinition, der durch die Schnittstellenextrahierung führt, sehen Sie in Abbildung 8.18. Es werden alle öffentlichen (public) Mitglieder angezeigt, die sich potenziell zur Bildung der Schnittstelle anbieten. Sie müssen nur noch die schnittstellenbildenden Mitglieder markieren. Geben Sie der Schnittstelle auch noch einen passenden Namen, und tragen Sie die Datei ein, in der die Interface-Definition gespeichert werden soll, sofern Sie von den Vorgaben abweichen wollen.
 Hier klicken, um das Bild zu vergrößern
Abbildung 8.18 Dialog zum Extrahieren einer Schnittstelle
Vorausgesetzt, alle drei öffentlichen Member wurden ausgewählt, wird die neue Schnittstellendefinition wie folgt aussehen:
| interface ITestClass {
|
| int MyProperty { get; set; }
|
| event EventHandler TestEvent;
|
| void TestProc1();
|
| }
|
8.3.5 Beeinflussung der Parameterliste
 
Mit den letzten drei Umgestaltungsoptionen haben Sie, je nach gewählter Option, Einfluss auf die Parameterliste von Methoden, Indexern, Delegaten und Konstruktoren:
|
Lokale Variable auf Parameter heraufstufen |
|
Parameter entfernen |
|
Parameter neu anordnen |
Lokale Variable auf Parameter heraufstufen
Eine lokale Variable auf Parameter heraufzustufen ist ein Vorgang, mit dem Sie eine lokal verwendete Variable in einer Methode, einem Indexer oder einem Konstruktor als Parameter einfügen können. Alle Aufrufe werden entsprechend aktualisiert. Bedingung ist, dass der lokalen Variablen ein Wert zugewiesen wird.
Positionieren Sie dazu den Mauscursor auf die heraufzustufende Variable. Rufen Sie danach Lokale Variable auf Parameter heraufstufen auf, indem Sie den Befehl aus dem Menü Umgestalten auswählen. Die Variable wird der Parameterliste für das jeweilige Member am Ende hinzugefügt.
Im folgenden Codefragment ist in der Methode TestProc von TestClass die lokale Variable lngVar definiert, die im nächsten Schritt auf einen Parameter heraufgestuft werden soll.
| class Program {
|
| static void Main(string[] args) {
|
| TestClass obj = new TestClass();
|
| obj.TestProc(10);
|
| }
|
| }
|
| public class TestClass {
|
| public void TestProc(int intVar) {
|
| long lngVar = 5;
|
| // Anweisungen
|
| }
|
| }
|
Das Resultat der Operation ist wie folgt:
| class Program {
|
| static void Main(string[] args) {
|
| TestClass obj = new TestClass();
|
| obj.TestProc(10, 5);
|
| }
|
| }
|
| public class TestClass {
|
| public void TestProc(int intVar, long lngVar) {
|
| // Anweisungen
|
| }
|
| }
|
Beachten Sie, dass der Aufruf in Main sich automatisch den neuen Gegebenheiten angepasst hat.
Parameter entfernen
Parameter entfernen ist ein Vorgang, mit dem Sie problemlos Parameter aus Methoden, Konstruktoren, Indexern oder Delegaten entfernen können. Alle Aufrufe an das Member werden an die neue Parameterliste angepasst. Der Ablauf der Operation ähnelt der, die wir bei der Heraufstufung schon gesehen haben. In einem Dialog geben Sie die Parameter an, die gelöscht werden sollen.
 Hier klicken, um das Bild zu vergrößern
Abbildung 8.19 Löschen eines Parameters
In Abbildung 8.19 wurde der int-Parameter gelöscht markiert. Die Bestätigung erfolgt durch die Taste OK, falls Vorschau der Verweisänderungen deselektiert ist. Falls Sie sich die Vorschau anzeigen lassen, bestätigen Sie den Vorgang in diesem Fenster.
Parameter neu anordnen
Eine Neuanordnung hat keinen Einfluss auf die Programmlogik und wird wohl am ehesten in Betracht gezogen werden, wenn eine Methode vielfach überladen ist. Mit einer Neuanordnung lassen sich die Parameter in eine überschaubare, ähnliche Reihenfolge zwingen. Sie können aber auch die Parameterliste von Indexern, Delegaten und Konstruktoren neu anordnen.
 Hier klicken, um das Bild zu vergrößern
Abbildung 8.20 Neuanordnung der Parameter einer Parameterliste |